import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import socket
import threading
import ipaddress
from concurrent.futures import ThreadPoolExecutor, as_completed

class PortScannerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("端口扫描器")
        self.root.geometry("700x600")
        
        # 居中显示窗口
        self.center_window()
        
        # 创建界面元素
        self.create_widgets()
        
        # 扫描状态变量
        self.is_scanning = False
        self.scan_thread = None
        
    def center_window(self):
        """将窗口居中显示在屏幕中央"""
        self.root.update_idletasks()
        width = self.root.winfo_width()
        height = self.root.winfo_height()
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry(f'{width}x{height}+{x}+{y}')
        
    def create_widgets(self):
        """创建GUI界面元素"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky='we')
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # IP地址输入
        ttk.Label(main_frame, text="目标IP地址:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.ip_entry = ttk.Entry(main_frame, width=30)
        self.ip_entry.grid(row=0, column=1, sticky='we', pady=5)
        self.ip_entry.insert(0, "192.168.1.1")
        
        # IP地址说明
        ip_help_text = "输入单个IP地址(如: 192.168.1.1)或CIDR格式(如: 192.168.1.0/24)或IP范围(如: 192.168.1.1-192.168.1.200)"
        self.ip_help_label = ttk.Label(main_frame, text=ip_help_text, font=("Arial", 10))
        self.ip_help_label.grid(row=1, column=1, sticky='w', pady=(0, 5))
        
        # 端口范围输入
        ttk.Label(main_frame, text="端口范围:").grid(row=2, column=0, sticky=tk.W, pady=5)
        port_frame = ttk.Frame(main_frame)
        port_frame.grid(row=2, column=1, sticky='we', pady=5)
        port_frame.columnconfigure(0, weight=1)
        port_frame.columnconfigure(1, weight=1)
        port_frame.columnconfigure(2, weight=1)
        
        self.port_start_entry = ttk.Entry(port_frame, width=10)
        self.port_start_entry.grid(row=0, column=0, sticky=tk.W)
        self.port_start_entry.insert(0, "1")
        
        ttk.Label(port_frame, text="-").grid(row=0, column=1)
        
        self.port_end_entry = ttk.Entry(port_frame, width=10)
        self.port_end_entry.grid(row=0, column=2, sticky=tk.W)
        self.port_end_entry.insert(0, "65535")
        
        # 单个端口选项
        self.single_port_var = tk.BooleanVar()
        self.single_port_check = ttk.Checkbutton(
            main_frame, 
            text="扫描单个端口", 
            variable=self.single_port_var,
            command=self.toggle_port_mode
        )
        self.single_port_check.grid(row=3, column=1, sticky=tk.W, pady=5)
        
        # 单个端口输入
        self.single_port_frame = ttk.Frame(main_frame)
        self.single_port_frame.grid(row=4, column=1, sticky='we', pady=5)
        self.single_port_frame.columnconfigure(0, weight=1)
        self.single_port_label = ttk.Label(self.single_port_frame, text="端口号:")
        self.single_port_label.grid(row=0, column=0, sticky=tk.W)
        self.single_port_entry = ttk.Entry(self.single_port_frame, width=10)
        self.single_port_entry.grid(row=0, column=1, sticky=tk.W)
        self.single_port_entry.insert(0, "80")
        self.single_port_frame.grid_remove()  # 默认隐藏
        
        # 扫描按钮
        self.scan_button = ttk.Button(main_frame, text="开始扫描", command=self.start_scan)
        self.scan_button.grid(row=5, column=0, columnspan=2, pady=10)
        
        # 结果显示区域
        ttk.Label(main_frame, text="扫描结果:").grid(row=6, column=0, sticky=tk.W, pady=(10, 5))
        
        # 滚动文本框显示结果
        self.result_text = scrolledtext.ScrolledText(main_frame, height=20)
        self.result_text.grid(row=7, column=0, columnspan=2, sticky='wens', pady=5)
        main_frame.rowconfigure(7, weight=1)
        
        # 进度条
        self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
        self.progress.grid(row=8, column=0, columnspan=2, sticky='we', pady=5)
        
        # 状态标签
        self.status_label = ttk.Label(main_frame, text="就绪")
        self.status_label.grid(row=8, column=0, columnspan=2, pady=5)
        
        # 版权信息
        self.copyright_label = ttk.Label(
            main_frame, 
            text="速光网络软件开发 关注抖音号：dubaishun12",
            font=("Arial", 13),
            foreground="gray"
        )
        self.copyright_label.grid(row=9, column=0, columnspan=2, pady=10, sticky='n')
        main_frame.rowconfigure(9, weight=0)
        
    def toggle_port_mode(self):
        """切换端口模式（范围扫描/单端口扫描）"""
        if self.single_port_var.get():
            # 显示单端口输入框
            self.single_port_frame.grid()
        else:
            # 隐藏单端口输入框
            self.single_port_frame.grid_remove()
            
    def parse_ip_range(self, ip_input):
        """解析IP输入，支持单个IP、CIDR和IP范围格式"""
        ip_list = []
        
        # 检查是否为IP范围格式 (如: 192.168.1.1-192.168.1.200)
        if '-' in ip_input:
            try:
                start_ip, end_ip = ip_input.split('-')
                start_ip = start_ip.strip()
                end_ip = end_ip.strip()
                
                # 解析起始和结束IP
                start = ipaddress.ip_address(start_ip)
                end = ipaddress.ip_address(end_ip)
                
                # 为了简化，我们只处理同一子网内的IP范围
                # 在实际应用中，可能需要更复杂的处理
                ip_list.append(str(start))
                # 这里可以扩展为生成范围内的所有IP
                
            except ValueError:
                # 如果解析失败，返回原始输入
                ip_list.append(ip_input)
                
        # 检查是否为CIDR格式 (如: 192.168.1.0/24)
        elif '/' in ip_input:
            try:
                network = ipaddress.ip_network(ip_input, strict=False)
                # 为了性能考虑，我们只取网络中的前10个IP进行扫描
                # 在实际应用中，可能需要让用户选择扫描所有IP或部分IP
                count = 0
                for ip in network.hosts():
                    if count >= 10:  # 限制扫描数量以提高性能
                        break
                    ip_list.append(str(ip))
                    count += 1
            except ValueError:
                # 如果解析失败，返回原始输入
                ip_list.append(ip_input)
        else:
            # 单个IP地址
            ip_list.append(ip_input)
            
        return ip_list
            
    def validate_inputs(self):
        """验证用户输入"""
        ip = self.ip_entry.get().strip()
        if not ip:
            messagebox.showerror("错误", "请输入目标IP地址")
            return False
            
        # 检查是否为IP范围格式 (如: 192.168.1.1-192.168.1.200)
        if '-' in ip:
            try:
                start_ip, end_ip = ip.split('-')
                ipaddress.ip_address(start_ip.strip())
                # 如果end_ip不是完整IP地址，则构造完整地址
                if '.' not in end_ip:
                    messagebox.showerror("错误", "IP范围格式不正确，请使用完整IP地址")
                    return False
                ipaddress.ip_address(end_ip.strip())
                return True
            except ValueError:
                messagebox.showerror("错误", "请输入有效的IP范围格式 (如: 192.168.1.1-192.168.1.200)")
                return False
                
        # 检查是否为CIDR格式 (如: 192.168.1.0/24)
        if '/' in ip:
            try:
                ipaddress.ip_network(ip, strict=False)
                return True
            except ValueError:
                messagebox.showerror("错误", "请输入有效的CIDR格式 (如: 192.168.1.0/24)")
                return False
        
        try:
            # 验证单个IP地址格式
            ipaddress.ip_address(ip)
        except ipaddress.AddressValueError:
            messagebox.showerror("错误", "请输入有效的IP地址")
            return False
            
        return True
        
    def start_scan(self):
        """开始扫描"""
        if not self.validate_inputs():
            return
            
        if self.is_scanning:
            self.stop_scan()
            return
            
        self.is_scanning = True
        self.scan_button.config(text="停止扫描")
        self.result_text.delete(1.0, tk.END)
        self.progress.start()
        self.status_label.config(text="正在扫描...")
        
        # 在新线程中执行扫描
        self.scan_thread = threading.Thread(target=self.scan_ports)
        self.scan_thread.daemon = True
        self.scan_thread.start()
        
    def stop_scan(self):
        """停止扫描"""
        self.is_scanning = False
        self.scan_button.config(text="开始扫描")
        self.progress.stop()
        self.status_label.config(text="扫描已停止")
        
    def scan_port(self, ip, port):
        """扫描单个端口"""
        try:
            # 创建socket连接
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(1)  # 恢复到1秒超时
            
            # 尝试连接
            result = sock.connect_ex((ip, port))
            sock.close()
            
            # 如果返回值为0，表示端口开放
            return result == 0
        except Exception as e:
            # 记录异常但不中断扫描
            return False
    
    def scan_port_detailed(self, ip, port):
        """详细扫描单个端口，提供更多信息"""
        try:
            # 创建socket连接
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(1)  # 恢复到1秒超时
            
            # 尝试连接
            result = sock.connect_ex((ip, port))
            
            # 获取更多连接信息
            if result == 0:
                # 端口开放
                try:
                    # 尝试获取服务信息
                    service = socket.getservbyport(port)
                except:
                    service = "unknown"
                sock.close()
                return True, service
            else:
                # 端口关闭或其他状态
                sock.close()
                return False, None
        except Exception as e:
            # 记录异常但不中断扫描
            return False, None

    def scan_ports(self):
        """执行端口扫描"""
        ip_input = self.ip_entry.get().strip()
        open_ports = []
        
        # 解析IP输入
        ip_list = self.parse_ip_range(ip_input)
        
        try:
            total_ips = len(ip_list)
            for idx, current_ip in enumerate(ip_list):
                if not self.is_scanning:
                    break
                    
                self.status_label.config(text=f"正在扫描 {current_ip} ({idx+1}/{total_ips})...")
                self.root.update_idletasks()
                
                if self.single_port_var.get():
                    # 扫描单个端口
                    port = int(self.single_port_entry.get().strip())
                    if self.scan_port(current_ip, port):
                        open_ports.append((current_ip, port))
                        self.result_text.insert(tk.END, f"{current_ip} 端口 {port} 开放\n")
                        self.result_text.see(tk.END)
                        self.root.update_idletasks()
                else:
                    # 扫描端口范围
                    start_port = int(self.port_start_entry.get().strip())
                    end_port = int(self.port_end_entry.get().strip())
                    total_ports = end_port - start_port + 1
                    
                    # 使用线程池提高扫描速度
                    with ThreadPoolExecutor(max_workers=100) as executor:
                        # 提交所有扫描任务
                        future_to_port = {
                            executor.submit(self.scan_port, current_ip, port): port 
                            for port in range(start_port, end_port + 1)
                        }
                        
                        scanned = 0
                        for future in as_completed(future_to_port):
                            if not self.is_scanning:
                                break
                                
                            port = future_to_port[future]
                            try:
                                if future.result():
                                    open_ports.append((current_ip, port))
                                    self.result_text.insert(tk.END, f"{current_ip} 端口 {port} 开放\n")
                                    self.result_text.see(tk.END)
                            except Exception as e:
                                pass
                                
                            scanned += 1
                            if scanned % 100 == 0:
                                self.status_label.config(text=f"正在扫描 {current_ip} ({scanned}/{total_ports} 个端口)")
                                self.root.update_idletasks()
                                
        except Exception as e:
            self.result_text.insert(tk.END, f"扫描过程中发生错误: {str(e)}\n")
            
        # 扫描完成
        self.is_scanning = False
        self.scan_button.config(text="开始扫描")
        self.progress.stop()
        self.status_label.config(
            text=f"扫描完成，找到 {len(open_ports)} 个开放端口"
        )
def main():
    root = tk.Tk()
    app = PortScannerGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()